package com.googlecode.groovy_toy_orm.framework


import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.testng.Reporter
import org.testng.annotations.AfterMethod
import org.testng.annotations.BeforeClass
import org.testng.annotations.BeforeMethod

import java.lang.reflect.Method

/**
 * @author: DavydovMD
 * Date: 01.03.13
 * Time: 14:46
 */
abstract class BaseFunctionalTest {
    @Delegate Assertions assertions;
    public static final Logger logger = LoggerFactory.getLogger("MainLogger")

    private static InheritableThreadLocal<Integer> stepCounter = new InheritableThreadLocal<Integer>() {
        @Override
        Integer initialValue() {
            0
        }
    } as InheritableThreadLocal<Integer>

    private static ThreadLocal<Method> testMethodThreadLocal = new ThreadLocal<Method>();
    private static ThreadLocal<Object[]> testMethodParametersThreadLocal = new ThreadLocal<Object[]>();

    @BeforeClass
    public void b4(){
        Reporter.setEscapeHtml(false)
    }

    @BeforeMethod
    public static void beforeTest(Method method, Object[] parameters) {
        stepCounter.set(0)
        testMethodThreadLocal.set(method)
        testMethodParametersThreadLocal.set(parameters)
    }

    @AfterMethod
    public static void afterTest() {
        testMethodThreadLocal.set(null)
        testMethodParametersThreadLocal.set(null)
    }

//    private static InheritableThreadLocal<UUID> currentStepId = new InheritableThreadLocal<UUID>()
    private static InheritableThreadLocal<StepInfo> currentStepInfo = new InheritableThreadLocal<StepInfo>();

    public static StepInfo getCurrentStepInfo() {
        currentStepInfo.get()
    }


    public static int getIndentationLevel() {
        StepInfo si = currentStepInfo.get()
        int indentationLevel = 0;
        while (si?.getParent() != null) {
            si = si?.getParent()
            indentationLevel++
        }
        indentationLevel
    }

    public static String getIndentation() {
        String indentation = ""
        getIndentationLevel().times { indentation += "\t" }
        indentation
    }

    // @Immutable
    public static class StepInfo {
        final UUID id = UUID.randomUUID()
        final Date started = new Date()
        String name
        String description
        StepInfo parent
        boolean failed
        Date finished


        public boolean isChild(BaseFunctionalTest.StepInfo step) {
            StepInfo curStep = step;
            while (curStep != null && curStep != this) {
                curStep = curStep?.getParent()
            }
            return (curStep == this);
        }

        public boolean isChildOrSelf(BaseFunctionalTest.StepInfo step) {
            if (this == step) return true;
            isChild(step)
        }
    }
    /**
     * All test methods should contain only one call -> this :)
     * @param closure body of a Test. Should contain calls to asStep() method only
     */

    def asTest(Closure closure) {
        try {
            currentStepInfo.set(new StepInfo(name: testMethodThreadLocal.get().getName(), description: testMethodParametersThreadLocal.get().toString()))
            logger.info("==[Test started:${currentStepInfo.get().getName()}:${currentStepInfo.get().getDescription()} at ${currentStepInfo.get().getStarted()}]==")
            closure.call()
        } catch (any) {
            throw any
        } finally {
            currentStepInfo.get()?.setFinished(new Date())

            if (Assertions.getFailedWeakAsserts().asBoolean()){
                logger.error("Some weak asserts failed ${Assertions.getFailedWeakAsserts()}")
            }


            logger.info("==[Test finished:${currentStepInfo.get().getName()}:${currentStepInfo.get().getDescription()} at ${currentStepInfo.get().getStarted()}]==")
            println();
            println();
            currentStepInfo.set(null)
        }
        Assertions.onTestFinish()
    }

    /**
     * Should be used within asTest() closure
     * @param closure step body.
     * @return result, returned by closure
     */

    static Object asStep(Closure closure) {
        asStep(null, null, false, closure)
    }

    /**
     * Should be used within asTest() closure
     * @param closure step body.
     * @return result, returned by closure
     */

    static Object asStep(String stepName, Closure closure) {
        asStep(stepName, null, false, closure)
    }


    /**
     * Should be used within asTest() closure
     * @param closure step body.
     * @return result, returned by closure
     */
    static Object asStep(boolean failsTest, Closure closure) {
        asStep(null, null, failsTest, closure)
    }

    /**
     * Should be used within asTest() closure
     * @param closure step body.
     * @return result, returned by closure
     */

    static Object asStep(String stepName, boolean failsTest, Closure closure) {
        asStep(stepName, null, failsTest, closure)
    }

    /**
     * Should be used within asTest() closure
     * @param closure step body.
     * @return result, returned by closure
     */

    static Object asStep(String stepName, String stepDescription, Closure closure) {
        asStep(stepName, stepDescription, false, closure)
    }

    /**
     * Should be used within asTest() closure
     * @param closure step body.
     * @param failsTest set to true if you want weekAsserts within step to fail the test
     * @return result, returned by closure
     */

    static Object asStep(String stepName, String stepDescription, boolean failsTest, Closure closure) {
        stepCounter.set(stepCounter.get() + 1)

        StepInfo parentStep = currentStepInfo.get()


        if (!stepName) {
            stepName = "step#${stepCounter.get()}"
        }
        if (!stepDescription) {
            stepDescription = ""
        }


        logger.info("-->[Step started:${stepName} ${stepDescription ?: ""} ]")
        currentStepInfo.set(new StepInfo(name: stepName, description: stepDescription, parent: parentStep))

        def ret = null
        try {
            ret = closure.call()
        } catch (any) {
            throw any
        } finally {
            //println()
            Collection<FailedAssert> failedAsserts = Assertions.getStepFailedAssertions(currentStepInfo.get())
            currentStepInfo.get().setFailed(failedAsserts.asBoolean())
            boolean isFailed = currentStepInfo.get().getFailed()
            currentStepInfo.set(parentStep)
            logger.info("<--[Step finished:${stepName} ${stepDescription ?: ""}]")
            if (failsTest && isFailed) {
                throw new AssertionError(failedAsserts.toString())
            }
        }
        ret
    }


}
